﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Remoting.Reactive.ProtoBuf
{
    public sealed class ExpressionTranslator : IExpressionTranslator
    {
        private readonly IBinarySerializer _binarySerializer;

        public ExpressionTranslator(IBinarySerializer binarySerializer)
        {
            Guard.Requires<ArgumentNullException>(binarySerializer != null);
            _binarySerializer = binarySerializer;
        }

        public ExpressionDto Translate(Expression expression)
        {
            if (expression == null)
                return null;
            switch (expression.NodeType)
            {
                case ExpressionType.Negate:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.ArrayLength:
                case ExpressionType.Quote:
                case ExpressionType.TypeAs:
                    return TranslateUnary((UnaryExpression)expression);
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.Coalesce:
                case ExpressionType.ArrayIndex:
                case ExpressionType.RightShift:
                case ExpressionType.LeftShift:
                case ExpressionType.ExclusiveOr:
                    return TranslateBinary((BinaryExpression)expression);
                case ExpressionType.TypeIs:
                    return TranslateTypeIs((TypeBinaryExpression)expression);
                case ExpressionType.Conditional:
                    return TranslateConditional((ConditionalExpression)expression);
                case ExpressionType.Constant:
                    return TranslateConstant((ConstantExpression)expression);
                case ExpressionType.Parameter:
                    return TranslateParameter((ParameterExpression)expression);
                case ExpressionType.MemberAccess:
                    return TranslateMemberAccess((MemberExpression)expression);
                case ExpressionType.Call:
                    return TranslateMethodCall((MethodCallExpression)expression);
                case ExpressionType.Lambda:
                    return TranslateLambda((LambdaExpression)expression);
                case ExpressionType.New:
                    return TranslateNew((NewExpression)expression);
                case ExpressionType.NewArrayInit:
                case ExpressionType.NewArrayBounds:
                    return TranslateNewArray((NewArrayExpression)expression);
                case ExpressionType.Invoke:
                    return TranslateInvocation((InvocationExpression)expression);
                case ExpressionType.MemberInit:
                    return TranslateMemberInit((MemberInitExpression)expression);
                case ExpressionType.ListInit:
                    return TranslateListInit((ListInitExpression)expression);
                default:
                    throw new Exception(string.Format("Unhandled expression type: '{0}'", expression.NodeType));
            }
        }

        private MemberBindingDto TranslateBinding(MemberBinding binding)
        {
            switch (binding.BindingType)
            {
                case MemberBindingType.Assignment:
                    return TranslateMemberAssignment((MemberAssignment)binding);
                case MemberBindingType.MemberBinding:
                    return TranslateMemberMemberBinding((MemberMemberBinding)binding);
                case MemberBindingType.ListBinding:
                    return TranslateMemberListBinding((MemberListBinding)binding);
                default:
                    throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType));
            }
        }

        private ElementInitDto TranslateElementInitializer(ElementInit initializer)
        {
            var addMethod = Translate(initializer.AddMethod);
            var arguments = TranslateExpressionList(initializer.Arguments).ToArray();
            return new ElementInitDto { AddMethod = addMethod, Arguments = arguments };
        }

        private ExpressionDto TranslateUnary(UnaryExpression unaryExpression)
        {
            var nodeType = Translate(unaryExpression.NodeType);
            var operand = Translate(unaryExpression.Operand);
            var type = Translate(unaryExpression.Type);
            var method = Translate(unaryExpression.Method);
            return new UnaryExpressionDto
                       {
                           NodeType = nodeType,
                           Operand = operand,
                           Type = type,
                           Method = method
                       };
        }

        private BinaryExpressionDto TranslateBinary(BinaryExpression binaryExpression)
        {
            var left = Translate(binaryExpression.Left);
            var right = Translate(binaryExpression.Right);
            var conversion = TranslateLambda(binaryExpression.Conversion);
            var method = Translate(binaryExpression.Method);
            return new BinaryExpressionDto
                       {
                           Left = left,
                           Right = right,
                           Conversion = conversion,
                           Method = method
                       };
        }

        private TypeBinaryExpressionDto TranslateTypeIs(TypeBinaryExpression typeBinaryExpression)
        {
            var expression = Translate(typeBinaryExpression.Expression);
            var typeOperand = Translate(typeBinaryExpression.TypeOperand);
            return new TypeBinaryExpressionDto
                       {
                           Expression = expression,
                           TypeOperand = typeOperand
                       };
        }

        private ConstantExpressionDto TranslateConstant(ConstantExpression constantExpression)
        {
            var type = Translate(constantExpression.Type);
            var payload = _binarySerializer.Serialize(constantExpression.Type, constantExpression.Value);
            return new ConstantExpressionDto
                       {
                           Type = type,
                           Payload = payload
                       };
        }

        private ConditionalExpressionDto TranslateConditional(ConditionalExpression c)
        {
            var test = Translate(c.Test);
            var ifTrue = Translate(c.IfTrue);
            var ifFalse = Translate(c.IfFalse);
            return new ConditionalExpressionDto
                       {
                           Test = test,
                           IfTrue = ifTrue,
                           IfFalse = ifFalse
                       };
        }

        private ParameterExpressionDto TranslateParameter(ParameterExpression p)
        {
            return new ParameterExpressionDto
                       {

                       };
        }

        private MemberExpressionDto TranslateMemberAccess(MemberExpression m)
        {
            var exp = Translate(m.Expression);
            return new MemberExpressionDto
                       {
                           Expression = exp
                       };
        }

        private MethodCallExpressionDto TranslateMethodCall(MethodCallExpression methodCallExpression)
        {
            var target = Translate(methodCallExpression.Object);
            var method = Translate(methodCallExpression.Method);
            var arguments = TranslateExpressionList(methodCallExpression.Arguments).ToArray();
            return new MethodCallExpressionDto
                       {
                           Object = target,
                           MethodInfo = method,
                           Arguments = arguments
                       };
        }

        private IEnumerable<ExpressionDto> TranslateExpressionList(IEnumerable<Expression> expressions)
        {
            return expressions.Select(Translate);
        }

        public MemberAssignmentDto TranslateMemberAssignment(MemberAssignment assignment)
        {
            var bindingType = Translate(assignment.BindingType);
            var expression = Translate(assignment.Expression);
            var member = Translate(assignment.Member);
            return new MemberAssignmentDto
                       {
                           BindingType = bindingType,
                           Expression = expression,
                           Member = member
                       };
        }

        private MemberMemberBindingDto TranslateMemberMemberBinding(MemberMemberBinding binding)
        {
            var bindings = TranslateBindingList(binding.Bindings).ToArray();
            return new MemberMemberBindingDto
                       {
                           Bindings = bindings
                       };
        }

        private MemberListBindingDto TranslateMemberListBinding(MemberListBinding binding)
        {
            var member = Translate(binding.Member);
            var initializers = TranslateElementInitializerList(binding.Initializers).ToArray();
            return new MemberListBindingDto
                       {
                           Member = member,
                           Initializers = initializers
                       };
        }

        private IEnumerable<MemberBindingDto> TranslateBindingList(IEnumerable<MemberBinding> original)
        {
            return original.Select(TranslateBinding);
        }

        private IEnumerable<ElementInitDto> TranslateElementInitializerList(IEnumerable<ElementInit> original)
        {
            return original.Select(TranslateElementInitializer);
        }

        public LambdaExpressionDto TranslateLambda(LambdaExpression lambda)
        {
            var type = Translate(lambda.Type);
            var body = Translate(lambda.Body);
            var parameters = TranslateParameters(lambda.Parameters);
            return new LambdaExpressionDto
                       {
                           Type = type,
                           Body = body,
                           Parameters = parameters
                       };
        }

        private NewExpressionDto TranslateNew(NewExpression nex)
        {
            var args = TranslateExpressionList(nex.Arguments);
            if (args != nex.Arguments)
            {
                if (nex.Members != null)
                    return Expression.New(nex.Constructor, args, nex.Members);
                else
                    return Expression.New(nex.Constructor, args);
            }
            return nex;
        }

        private ExpressionDto TranslateMemberInit(MemberInitExpression init)
        {
            var n = TranslateNew(init.NewExpression);
            var bindings = TranslateBindingList(init.Bindings);
            if (n != init.NewExpression || bindings != init.Bindings)
            {
                return Expression.MemberInit(n, bindings);
            }
            return init;
        }

        private ExpressionDto TranslateListInit(ListInitExpression init)
        {
            var n = TranslateNew(init.NewExpression);
            var initializers = TranslateElementInitializerList(init.Initializers);
            if (n != init.NewExpression || initializers != init.Initializers)
            {
                return Expression.ListInit(n, initializers);
            }
            return init;
        }

        private ExpressionDto TranslateNewArray(NewArrayExpression na)
        {
            var exprs = TranslateExpressionList(na.Expressions);
            if (exprs != na.Expressions)
            {
                if (na.NodeType == ExpressionType.NewArrayInit)
                {
                    return Expression.NewArrayInit(na.Type.GetElementType(), exprs);
                }
                else
                {
                    return Expression.NewArrayBounds(na.Type.GetElementType(), exprs);
                }
            }
            return na;
        }

        private ExpressionDto TranslateInvocation(InvocationExpression iv)
        {
            var args = TranslateExpressionList(iv.Arguments);
            var expr = Translate(iv.Expression);
            if (args != iv.Arguments || expr != iv.Expression)
            {
                return Expression.Invoke(expr, args);
            }
            return iv;
        }

        private static ExpressionTypeDto Translate(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.Add:
                    return ExpressionTypeDto.Add;
                case ExpressionType.AddChecked:
                    return ExpressionTypeDto.AddChecked;
                case ExpressionType.And:
                    return ExpressionTypeDto.And;
                case ExpressionType.AndAlso:
                    return ExpressionTypeDto.AndAlso;
                case ExpressionType.ArrayLength:
                    return ExpressionTypeDto.ArrayLength;
                case ExpressionType.ArrayIndex:
                    return ExpressionTypeDto.ArrayIndex;
                case ExpressionType.Call:
                    return ExpressionTypeDto.Call;
                case ExpressionType.Coalesce:
                    return ExpressionTypeDto.Coalesce;
                case ExpressionType.Conditional:
                    return ExpressionTypeDto.Conditional;
                case ExpressionType.Constant:
                    return ExpressionTypeDto.Constant;
                case ExpressionType.Convert:
                    return ExpressionTypeDto.Convert;
                case ExpressionType.ConvertChecked:
                    return ExpressionTypeDto.ConvertChecked;
                case ExpressionType.Divide:
                    return ExpressionTypeDto.Divide;
                case ExpressionType.Equal:
                    return ExpressionTypeDto.Equal;
                case ExpressionType.ExclusiveOr:
                    return ExpressionTypeDto.ExclusiveOr;
                case ExpressionType.GreaterThan:
                    return ExpressionTypeDto.GreaterThan;
                case ExpressionType.GreaterThanOrEqual:
                    return ExpressionTypeDto.GreaterThanOrEqual;
                case ExpressionType.Invoke:
                    return ExpressionTypeDto.Invoke;
                case ExpressionType.Lambda:
                    return ExpressionTypeDto.Lambda;
                case ExpressionType.LeftShift:
                    return ExpressionTypeDto.LeftShift;
                case ExpressionType.LessThan:
                    return ExpressionTypeDto.LessThan;
                case ExpressionType.LessThanOrEqual:
                    return ExpressionTypeDto.LessThanOrEqual;
                case ExpressionType.ListInit:
                    return ExpressionTypeDto.ListInit;
                case ExpressionType.MemberAccess:
                    return ExpressionTypeDto.MemberAccess;
                case ExpressionType.MemberInit:
                    return ExpressionTypeDto.MemberInit;
                case ExpressionType.Modulo:
                    return ExpressionTypeDto.Modulo;
                case ExpressionType.Multiply:
                    return ExpressionTypeDto.Multiply;
                case ExpressionType.MultiplyChecked:
                    return ExpressionTypeDto.MultiplyChecked;
                case ExpressionType.Negate:
                    return ExpressionTypeDto.Negate;
                case ExpressionType.UnaryPlus:
                    return ExpressionTypeDto.UnaryPlus;
                case ExpressionType.NegateChecked:
                    return ExpressionTypeDto.NegateChecked;
                case ExpressionType.New:
                    return ExpressionTypeDto.New;
                case ExpressionType.NewArrayInit:
                    return ExpressionTypeDto.NewArrayInit;
                case ExpressionType.NewArrayBounds:
                    return ExpressionTypeDto.NewArrayBounds;
                case ExpressionType.Not:
                    return ExpressionTypeDto.Not;
                case ExpressionType.NotEqual:
                    return ExpressionTypeDto.NotEqual;
                case ExpressionType.Or:
                    return ExpressionTypeDto.Or;
                case ExpressionType.OrElse:
                    return ExpressionTypeDto.OrElse;
                case ExpressionType.Parameter:
                    return ExpressionTypeDto.Parameter;
                case ExpressionType.Power:
                    return ExpressionTypeDto.Power;
                case ExpressionType.Quote:
                    return ExpressionTypeDto.Quote;
                case ExpressionType.RightShift:
                    return ExpressionTypeDto.RightShift;
                case ExpressionType.Subtract:
                    return ExpressionTypeDto.Subtract;
                case ExpressionType.SubtractChecked:
                    return ExpressionTypeDto.SubtractChecked;
                case ExpressionType.TypeAs:
                    return ExpressionTypeDto.TypeAs;
                case ExpressionType.TypeIs:
                    return ExpressionTypeDto.TypeIs;
                case ExpressionType.Assign:
                    return ExpressionTypeDto.Assign;
                case ExpressionType.Block:
                    return ExpressionTypeDto.Block;
                case ExpressionType.DebugInfo:
                    return ExpressionTypeDto.DebugInfo;
                case ExpressionType.Decrement:
                    return ExpressionTypeDto.Decrement;
                case ExpressionType.Dynamic:
                    return ExpressionTypeDto.Dynamic;
                case ExpressionType.Default:
                    return ExpressionTypeDto.Default;
                case ExpressionType.Extension:
                    return ExpressionTypeDto.Extension;
                case ExpressionType.Goto:
                    return ExpressionTypeDto.Goto;
                case ExpressionType.Increment:
                    return ExpressionTypeDto.Increment;
                case ExpressionType.Index:
                    return ExpressionTypeDto.Index;
                case ExpressionType.Label:
                    return ExpressionTypeDto.Label;
                case ExpressionType.RuntimeVariables:
                    return ExpressionTypeDto.RuntimeVariables;
                case ExpressionType.Loop:
                    return ExpressionTypeDto.Loop;
                case ExpressionType.Switch:
                    return ExpressionTypeDto.Switch;
                case ExpressionType.Throw:
                    return ExpressionTypeDto.Throw;
                case ExpressionType.Try:
                    return ExpressionTypeDto.Try;
                case ExpressionType.Unbox:
                    return ExpressionTypeDto.Unbox;
                case ExpressionType.AddAssign:
                    return ExpressionTypeDto.AddAssign;
                case ExpressionType.AndAssign:
                    return ExpressionTypeDto.AndAssign;
                case ExpressionType.DivideAssign:
                    return ExpressionTypeDto.DivideAssign;
                case ExpressionType.ExclusiveOrAssign:
                    return ExpressionTypeDto.ExclusiveOrAssign;
                case ExpressionType.LeftShiftAssign:
                    return ExpressionTypeDto.LeftShiftAssign;
                case ExpressionType.ModuloAssign:
                    return ExpressionTypeDto.ModuloAssign;
                case ExpressionType.MultiplyAssign:
                    return ExpressionTypeDto.MultiplyAssign;
                case ExpressionType.OrAssign:
                    return ExpressionTypeDto.OrAssign;
                case ExpressionType.PowerAssign:
                    return ExpressionTypeDto.PowerAssign;
                case ExpressionType.RightShiftAssign:
                    return ExpressionTypeDto.RightShiftAssign;
                case ExpressionType.SubtractAssign:
                    return ExpressionTypeDto.SubtractAssign;
                case ExpressionType.AddAssignChecked:
                    return ExpressionTypeDto.AddAssignChecked;
                case ExpressionType.MultiplyAssignChecked:
                    return ExpressionTypeDto.MultiplyAssignChecked;
                case ExpressionType.SubtractAssignChecked:
                    return ExpressionTypeDto.SubtractAssignChecked;
                case ExpressionType.PreIncrementAssign:
                    return ExpressionTypeDto.PreIncrementAssign;
                case ExpressionType.PreDecrementAssign:
                    return ExpressionTypeDto.PreDecrementAssign;
                case ExpressionType.PostIncrementAssign:
                    return ExpressionTypeDto.PostIncrementAssign;
                case ExpressionType.PostDecrementAssign:
                    return ExpressionTypeDto.PostDecrementAssign;
                case ExpressionType.TypeEqual:
                    return ExpressionTypeDto.TypeEqual;
                case ExpressionType.OnesComplement:
                    return ExpressionTypeDto.OnesComplement;
                case ExpressionType.IsTrue:
                    return ExpressionTypeDto.IsTrue;
                case ExpressionType.IsFalse:
                    return ExpressionTypeDto.IsFalse;
                default:
                    throw new ArgumentOutOfRangeException("expressionType");
            }
        }

        private static MemberBindingTypeDto Translate(MemberBindingType memberBindingType)
        {
            switch (memberBindingType)
            {
                case MemberBindingType.Assignment:
                    return MemberBindingTypeDto.Assignment;
                case MemberBindingType.MemberBinding:
                    return MemberBindingTypeDto.MemberBinding;
                case MemberBindingType.ListBinding:
                    return MemberBindingTypeDto.ListBinding;
                default:
                    throw new ArgumentOutOfRangeException("memberBindingType");
            }
        }

        private static TypeDto Translate(Type type)
        {
            return new TypeDto { FullName = type.FullName };
        }

        private static MemberInfoDto Translate(MemberInfo memberInfo)
        {
            throw new NotImplementedException();
        }

        private static MethodInfoDto Translate(MethodInfo methodInfo)
        {
            throw new NotImplementedException();
        }
    }
}
